package com.ho1ho.audioexample.utils;

import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.util.Log;

import com.ho1ho.audioexample.CustomApplication;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Michael Leo <y@ho1ho.com><br>
 * Date: 2020/02/11 10:03
 * <p>
 * Code is far away from bug with the animal protecting
 * </p>
 * <pre>
 * ----------Dragon be here!----------/
 *    ┏┓   ┏┓
 *   ┏┛┻━━━┛┻┓
 *   ┃       ┃
 *   ┃   ━   ┃
 *   ┃ ┳┛ ┗┳ ┃
 *   ┃       ┃
 *   ┃   ┻   ┃
 *   ┃       ┃
 *   ┗━┓   ┏━┛
 *     ┃   ┃神兽保佑
 *     ┃   ┃代码无BUG！
 *     ┃   ┗━━━┓
 *     ┃       ┣┓
 *     ┃       ┏┛
 *     ┗┓┓┏━┳┓┏┛
 *      ┃┫┫ ┃┫┫
 *      ┗┻┛ ┗┻┛
 * ━━━━━━神兽出没━━━━━━
 * </pre>
 */
public class AudioPlay {
    private static final String TAG = AudioPlay.class.getSimpleName();

    private ExecutorService mThreadPool = Executors.newFixedThreadPool(1);

    private static int mTrackBufferSize;
    private AudioTrack mAudioTrack;
    private MediaCodec mAudioDecoder;
    //    private AacDecoder mAacDecoder;
    private MediaExtractor mMediaExtractor;

    private boolean mIsPlaying;

    private int mSampleRate;
    private int mChannelMask;
    private int mAudioFormat;
    private byte[] mCsd0;

    public AudioPlay(int sampleRate, int channelMask, int audioFormat, byte[] csd0) {
        this(sampleRate, channelMask, audioFormat,
                AudioTrack.getMinBufferSize(sampleRate, channelMask, audioFormat) * 2, csd0);
    }

    public AudioPlay(int sampleRate, int channelMask, int audioFormat, int trackBufferSize, byte[] csd0) {
        Log.e(TAG, "trackBufferSize=" + trackBufferSize);

        mSampleRate = sampleRate;
        mChannelMask = channelMask;
        mAudioFormat = audioFormat;
        mTrackBufferSize = trackBufferSize;
        mCsd0 = csd0;

//        mAacDecoder = new AacDecoder(sampleRate,
//                channelMask,
//                audioFormat,
//                AUDIO_CSD_0,
//                bufferSize);
    }

    private void initAudioTrack() {
        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, // AudioManager.STREAM_MUSIC // AudioManager.STREAM_VOICE_CALL
                mSampleRate,
                mChannelMask,
                mAudioFormat,
                mTrackBufferSize,
                AudioTrack.MODE_STREAM);

//        AudioManager am = (AudioManager) CustomApplication.instance.getSystemService(Context.AUDIO_SERVICE);
//        am.setMode(AudioManager.MODE_IN_COMMUNICATION);
//        am.setSpeakerphoneOn(true);

        mAudioTrack.play();
    }

    // https://juejin.im/post/5c36bbad6fb9a049d975676b
    // https://stackoverflow.com/questions/56106877/how-to-decode-aac-formatmp4-audio-file-to-pcm-format-in-android
    // https://www.jianshu.com/p/b30d6a4f745b
    // https://blog.csdn.net/lavender1626/article/details/80431902
    // http://sohailaziz05.blogspot.com/2014/06/mediacodec-decoding-aac-android.html
    private void initAudioDecoder() {
        try {
            String folder = Objects.requireNonNull(CustomApplication.instance.getExternalFilesDir(null)).getAbsolutePath() + File.separator + "leo-audio";
            File mFilePath = new File(folder, "original.aac");

            mMediaExtractor = new MediaExtractor();
            mMediaExtractor.setDataSource(mFilePath.getAbsolutePath());

            MediaFormat format = mMediaExtractor.getTrackFormat(0);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith("audio")) {//获取音频轨道
                mMediaExtractor.selectTrack(0);//选择此音频轨道

                // 目前，若使用如下参数播放 AAC 文件的话，会导致程序闪退。原因应该是解码器参数设置不对导致的。
//                format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
//                format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
//                format.setInteger(MediaFormat.KEY_SAMPLE_RATE, AacEncoder.DEFAULT_SAMPLE_RATES);
//                format.setInteger(MediaFormat.KEY_BIT_RATE, AacEncoder.DEFAULT_BIT_RATES);
//                format.setInteger(MediaFormat.KEY_CHANNEL_MASK, CHANNEL_IN);
//                format.setInteger(MediaFormat.KEY_IS_ADTS, 1);
//                format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);

                mAudioDecoder = MediaCodec.createDecoderByType(mime);//创建Decode解码器
                format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, mSampleRate, 1);

                // ByteBuffer key
                // AAC Profile 5bits | 采样率 4bits | 声道数 4bits | 其他 3bits（一般都为0） |
                // 例如：AAC LC，44.1Khz，单声道，对应的数值是2，4，1,将其转换成2进制得到：0b10,0b100,0b1
                // 再将其按 AAC 格式要求，转换成对应的位数如下：
                // 00010 0100 0001 000
                // 其对应的16进制如下：
                // 0001 0010 0000 1000
                // 所以其 csd_0 值为 0x12，0x08
                ByteBuffer csd_0 = ByteBuffer.wrap(mCsd0);
                //ADTS头的解码信息
                format.setByteBuffer("csd-0", csd_0);

                mAudioDecoder.configure(format, null, null, 0);
            } else {
                return;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (mAudioDecoder == null) {
            Log.e("MA", "mAudioDecoder is null");
            return;
        }
        mAudioDecoder.start();//启动MediaCodec ，等待传入数据
    }

    public void playPcm() {
        mIsPlaying = true;

        initAudioTrack();

        String folder = Objects.requireNonNull(CustomApplication.instance.getExternalFilesDir(null)).getAbsolutePath() + File.separator + "leo-audio";
        File pcmFile = new File(folder, "original.pcm");

        mThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                byte[] pcmData = new byte[mTrackBufferSize];
                try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(pcmFile))) {
                    while (true) {
                        int readSize = is.read(pcmData, 0, pcmData.length);
                        if (readSize > 0) {
                            short[] playPartData = new short[pcmData.length / 2];
                            for (int i = 0; i < playPartData.length; i++) {
                                playPartData[i] = (short) ((pcmData[i * 2] & 0xFF) | (pcmData[i * 2 + 1] << 8));
                            }
                            mAudioTrack.write(playPartData, 0, playPartData.length);
                        } else {
                            break;
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public void playAac() {
        mIsPlaying = true;

        initAudioDecoder();
        initAudioTrack();

        mThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    boolean isFinish = false;
                    MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo();
                    while (!isFinish && mIsPlaying) {
                        int inputIndex = mAudioDecoder.dequeueInputBuffer(0);//获取可用的inputBuffer -1代表一直等待，0表示不等待 10000表示10毫秒超时
                        Log.w(TAG, "inputIndex=" + inputIndex);
                        if (inputIndex < 0) {
                            isFinish = true;
                        }
                        ByteBuffer inputBuffer = mAudioDecoder.getInputBuffer(inputIndex);
                        inputBuffer.clear();//清空之前传入inputBuffer内的数据
                        int sampleSize = mMediaExtractor.readSampleData(inputBuffer, 0);
                        byte[] sampleData = new byte[inputBuffer.remaining()];
                        inputBuffer.get(sampleData);
                        Log.i(TAG, "Sample aac data[" + sampleData.length + "]=" + Arrays.toString(sampleData));
                        if (sampleSize > 0) {
                            mAudioDecoder.queueInputBuffer(inputIndex, 0, sampleSize, 0  /*mMediaExtractor.getSampleTime()*/, 0); //通知解码器 解码
                            mMediaExtractor.advance(); //MediaExtractor移动到下一取样处
                        } else {
                            mAudioDecoder.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            isFinish = true;
                        }

                        int outputIndex = mAudioDecoder.dequeueOutputBuffer(decodeBufferInfo, 0);//获取解码得到的byte[]数据
                        Log.e(TAG, "outputIndex=" + outputIndex);

                        ByteBuffer outputBuffer;
                        byte[] chunkPCM;
                        //每次解码完成的数据不一定能一次吐出 所以用while循环，保证解码器吐出所有数据
                        while (outputIndex >= 0) {
                            outputBuffer = mAudioDecoder.getOutputBuffer(outputIndex);
                            chunkPCM = new byte[decodeBufferInfo.size];
                            outputBuffer.get(chunkPCM);
                            outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的，不清空下次会得到同样的数
                            // 播放解码后的PCM数据
                            if (chunkPCM.length > 0) {
//                                Log.i(TAG, "PCM data[" + chunkPCM.length + "]=" + Arrays.toString(chunkPCM));

                                short[] shortPcmData = new short[chunkPCM.length / 2];
                                for (int i = 0; i < shortPcmData.length; i++) {
                                    shortPcmData[i] = (short) ((chunkPCM[i * 2] & 0xFF) | (chunkPCM[i * 2 + 1] << 8));
                                }

                                Log.i(TAG, "Finally PCM data[" + shortPcmData.length + "]=" + Arrays.toString(shortPcmData));

                                mAudioTrack.write(shortPcmData, 0, shortPcmData.length);
                            }
                            mAudioDecoder.releaseOutputBuffer(outputIndex, false);
                            outputIndex = mAudioDecoder.dequeueOutputBuffer(decodeBufferInfo, 10_000);//再次获取数据
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    stop();
                }
            }
        });
    }

    public void stop() {
        mIsPlaying = false;

        if (mAudioDecoder != null) {
            try {
                mAudioDecoder.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (mAudioTrack != null) {
            try {
                mAudioTrack.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

//        if (mThreadPool != null) {
//            mThreadPool.shutdown();
//        }
    }

    public void release() {
        stop();

        if (mAudioDecoder != null) {
            try {
                mAudioDecoder.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
            mAudioDecoder = null;
        }
        if (mAudioTrack != null) {
            try {
                mAudioTrack.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
            mAudioTrack = null;
        }

        if (mMediaExtractor != null) {
            mMediaExtractor.release();
            mMediaExtractor = null;
        }

        if (mThreadPool != null) {
            mThreadPool.shutdownNow();
            mThreadPool = null;
        }
    }

    public boolean isPlaying() {
        return mIsPlaying;
    }

}
